/*
 * @Author: RuiLin
 * @Date: 2021-06-29 10:17:55
 * @Last Modified by: RuiLin
 * @Last Modified time: 2021-08-25 14:50:43
 */
<template>
  <el-form
    ref="form"
    class="form-wrapper"
    :size="size"
    :rules="rules"
    :label-width="labelWidth"
    :label-position="labelPosition"
    :model="form"
    :inline="inline"
    v-on="on"
    @submit.native.prevent
  >
    <FormItemWrapper :form-item-list="formItemList" :inline="inline" :form-items-map="formItemsMap" :span="span">
      <template #formItems="{col,idx}">
        <el-form-item
          :key="idx"
          :label-width="col.labelWidth"
          :label="col.label"
          :prop="col.key"
          :rules="col.rules"
        >
          <slot :name="`${col.key}_prefix`" :colKey="col.key" />
          <slot
            v-if="col.type === 'slot'"
            :col-key="col.key"
            :name="col.key"
            :form="form"
            :form-item-list="formItemList"
          />
          <template v-else>
            <!-- hack: component 加载el-radio-group报错-->
            <el-radio-group
              v-if="col.type === 'el-radio-group'"
              :ref="col.key"
              v-model="form[col.key]"
              v-bind="formItemsMap[col.key]['props']"
              v-on="col.on"
            >
              <el-radio
                v-for="option in formItemsMap[col.key]['options']"
                :key="option.value"
                :label="option.value"
              >{{ option.label }}</el-radio>
            </el-radio-group>
            <!-- 文件上传 -->
            <el-upload
              v-else-if="col.type === 'el-upload'"
              :ref="col.key"
              v-bind="formItemsMap[col.key]['props']"
              :file-list="form[col.key] || []"
              :on-exceed="_handleExceed(formItemsMap[col.key]['props'].limit)"
              :on-success="_handleSuccess(col.key)"
              :on-remove="_handleRemove(col.key)"
              :before-upload="
                _beforeUpload(
                  formItemsMap[col.key]['props'].allowFileTypes,
                  formItemsMap[col.key]['props'].size,
                  formItemsMap[col.key]['props'].typeStr
                )
              "
              v-on="col.on"
            >
              <template v-if="col.type === 'el-upload'">
                <el-button
                  size="mini"
                  plain
                  icon="el-icon-upload el-icon--right"
                >点击上传</el-button>
                <div slot="tip" class="el-upload__tip">
                  {{ formItemsMap[col.key]["props"].tipContent }}
                </div>
              </template>
            </el-upload>
            <span v-else-if="col.type === 'span'" class="span-content">
              {{ form[col.key] }}
            </span>
            <component
              :is="col.type"
              v-else
              :ref="col.key"
              v-model="form[col.key]"
              v-bind="formItemsMap[col.key]['props']"
              v-on="col.on"
              @keyup.enter.native="_handleInputEnter"
            >
              <!-- el-checkbox 项 -->
              <template v-if="col.type === 'el-checkbox-group'">
                <el-checkbox
                  v-for="option in formItemsMap[col.key]['options']"
                  :key="option.value"
                  :label="option.value"
                >{{ option.label }}</el-checkbox>
              </template>
              <!-- el-select 项 -->
              <template v-if="col.type === 'el-select'">
                <el-option
                  v-for="option in formItemsMap[col.key]['options']"
                  :key="option.value"
                  :label="option.label"
                  :value="option.value"
                />
              </template>
              <template v-if="col.type==='el-button'">
                {{ col.name }}
              </template>
            </component>
          </template>
          <slot :name="`${col.key}_append`" :colKey="col.key" />
        </el-form-item>
      </template>
    </FormItemWrapper>
    <div class="footer">
      <slot name="footer" />
    </div>
  </el-form>
</template>

<script>
import {
  elInput,
  elRadioGroup,
  elCheckboxGroup,
  elSelect,
  getElemMapping,
  elInputNumber,
  elTimeSelect,
  elDatePicker,
  elCascader
} from './predefine'
import FormItemWrapper from './formItemWrapper.vue'
export default {
  name: 'BaseForm',
  components: {
    FormItemWrapper
  },
  props: {
    size: {
      type: String,
      default: 'medium'
    },
    labelPosition: {
      type: String,
      default: 'right'
    },
    labelWidth: {
      type: String,
      default: '100px'
    },
    // 表单项默认宽度
    span: {
      type: Number,
      default: 24
    },
    // 表单校验规则
    rules: {
      type: Object,
      default() {
        return {}
      }
    },
    on: {
      type: Object,
      default() {
        return {}
      }
    },
    // 表单项配置
    formItems: {
      type: Array,
      default() {
        return []
      }
    },
    inline: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      // 表单对象
      form: {},
      // 初始化表单对象，用于表单重置
      defForm: {},
      // 表单项list
      formItemList: []
    }
  },
  computed: {
    formItemsMap() {
      const formCopy = { ...this.form }
      return this.formItemList.reduce(
        (accu, { key, type, label, ifRender, options, props }) => {
          const target = {}
          const _props =
            (typeof props === 'function' ? props(formCopy) : props) || {}
          _props.placeholder = this._getPlaceholder({
            type,
            label,
            placeholder: _props.placeholder
          })
          // 条件判断是否需要渲染
          target['isRender'] =
            typeof ifRender === 'function' ? !!ifRender(formCopy) : true
          const types = [elRadioGroup, elCheckboxGroup, elSelect, elCascader]
          if (types.includes(type)) {
            target['options'] = this._calcOptions(options, formCopy)
          }
          // 计算表单项props
          const elemItem = getElemMapping()[type] || {}
          const { defaultProps = {}} = elemItem
          target['props'] = Object.assign({}, defaultProps, { ..._props })
          accu[key] = target
          return accu
        },
        {}
      )
    }
  },
  watch: {
    formItems: {
      immediate: true,
      deep: true,
      handler(newVal) {
        if (!Array.isArray(newVal)) return
        const flatRows = this._flatRows(newVal)
        this.formItemList = flatRows
        // 备份初始化表单数据
        this.form = this._initForm(flatRows)
        this.effects = this._filterEffects(flatRows)
        this.defForm = JSON.parse(JSON.stringify(this.form))
      }
    },
    form: {
      deep: true,
      handler(newVal) {
        const effects = this.effects
        if (!Array.isArray(effects)) return
        effects.forEach((effect) => {
          typeof effect === 'function' && effect(newVal)
        })
      }
    }
  },
  methods: {
    // 初始化表单对象
    _initForm(formItems) {
      return formItems.reduce((accu, curr) => {
        const { key, type, defaultVal } = curr
        const elemMapping = getElemMapping()
        if (key && type !== 'slot') {
          accu[key] =
            defaultVal === undefined
              ? elemMapping[type].initialVal
              : defaultVal
        }
        return accu
      }, {})
    },

    // 判断数组是否为二维数组
    _isTwoDimensional(rows) {
      if (!Array.isArray(rows)) return
      return rows.some((row) => {
        return Array.isArray(row)
      })
    },

    _flatRows(rows) {
      if (!Array.isArray(rows)) return []
      const formatFormItem = (item, idx = 0) => {
        const formItem = Object.assign({}, item)
        formItem.rowIdx = idx
        const { on } = item
        formItem.on = this._extendEventHandlers(on)
        formItem.type = item.type || 'slot'
        return formItem
      }
      if (this._isTwoDimensional(rows)) {
        // 对二维数组配置做降维处理，便于内部计算使用
        return rows.reduce((accu, row, index) => {
          if (!Array.isArray(row)) return accu
          row = row.map((col) => formatFormItem(col, index))
          accu = [...accu, ...row]
          return accu
        }, [])
      } else {
        return rows.map((item) => formatFormItem(item))
      }
    },

    // 对 v-on 参数对象进行拓展，使之能同时接收form 对象作为参数
    _extendEventHandlers(on) {
      if (!on || on.constructor !== Object) return on
      const handlers = {}
      for (const key in on) {
        if (on.hasOwnProperty(key)) {
          handlers[key] = (event) => {
            on[key](event, this.form)
          }
        }
      }
      return handlers
    },
    // 初始化placeholder
    _getPlaceholder(item = {}) {
      const { type, label, placeholder } = item
      if (placeholder) return placeholder
      const text = typeof item.label === 'string' ? label : ''
      const inputTypes = [elInput, elInputNumber]
      if (inputTypes.includes(type)) return '请输入' + text
      const selectTypes = [elSelect, elTimeSelect, elDatePicker, elCascader]
      if (selectTypes.includes(type)) return '请选择' + text
    },
    // 收集form 表单的观察者
    _filterEffects(list) {
      const effects = []
      list.forEach(({ watchEffect }) => {
        if (typeof watchEffect === 'function') {
          effects.push(watchEffect)
        }
      })
      return effects
    },

    _calcOptions(options, formCopy) {
      let list = []
      if (Array.isArray(options)) {
        list = options
      }
      if (typeof options === 'function') {
        list = options(formCopy) || []
      }
      return list
    },

    _handleExceed(limit) {
      return (files, fileList) => {
        this.$message.warning(
          `当前限制选择 ${limit} 个文件，本次选择了 ${
            files.length
          } 个文件，共选择了 ${files.length + fileList.length} 个文件`
        )
      }
    },

    _handleRemove(key) {
      return (file) => {
        if (file && file.status === 'success') {
          const { uid } = file
          const idx = this.form[key].findIndex((file) => file.uid === uid)
          this.form[key].splice(idx, 1)
        }
      }
    },

    _handleSuccess(key) {
      return (res, file) => {
        const { result } = res
        file.url = result
        this.form[key].push(file)
      }
    },

    _beforeUpload(allowFileTypes, size) {
      const defAllowTypes = ['image/jpeg', 'image/jpg', 'image/png']
      const _size = size || 5
      return (file) => {
        const whiteList = Array.isArray(allowFileTypes)
          ? allowFileTypes
          : defAllowTypes
        const isAllow = whiteList.includes(file.type)
        const isLtSize = file.size / 1024 / 1024 < _size
        if (!isAllow) {
          this.$message.error('请检查文件格式')
        }
        if (!isLtSize) {
          this.$message.error(`文件大小不能超过${_size}MB!`)
        }

        return isAllow && isLtSize
      }
    },

    // 监听文本输入框enter 事件
    _handleInputEnter() {
      console.log('触发监听')
      this.$emit('enter', this.form)
    },

    getRef(key) {
      if (key === undefined) return this.$refs['form']
      return typeof key === 'string' ? this.$refs[key] || null : null
    },

    // 更新表单项options
    updateOptions(key, options = []) {
      if (!key || typeof key !== 'string') throw new Error('key值不正确')
      const target = this.formItemList.find((item) => {
        return item.key === key
      })
      if (!target) throw new Error(`${key}对应的表单项不存在`)
      this.$set(target, 'options', options)
    },

    // 更新表单项的值
    updateFormValue(form = {}) {
      if (form.constructor === Object) {
        Object.assign(this.form, form)
      } else {
        throw new Error('参数必须是对象')
      }
    },
    // 使用表单项默认值重置表单
    resetForm(cb) {
      this.form = Object.assign({}, this.form, this.defForm)
      cb && cb(this.form)
    },
    // 表单提交
    submit(cb, needValid = true) {
      const formElem = this.getRef()
      if (needValid) {
        formElem.validate((valid) => {
          cb && cb(valid, this.form)
        })
      } else {
        cb && cb(true, this.form)
      }
    }
  }
}
</script>
<style scoped lang='scss'>
.form-wrapper {
  .span-content {
    color: #141414;
    overflow-wrap: break-word;
  }
  .footer {
    @include flex(center, center);
  }
}
</style>
<style lang="scss">
.el-select {
    width: 100%;
  }
.el-form--inline {
  .el-form-item {
    margin-bottom: 10px !important;
    margin-top: 10px;
  }
  .el-checkbox {
    margin-left: 5px;
    .el-checkbox__label {
      padding-left: 5px;
    }
  }
}
</style>
